﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Windows.Forms;
using WinForms.Framework.Core;
using WinForms.Framework.Utility;

namespace WinForms.Framework.Controls
{
    /// <summary>
    /// System.Windows.Forms.DataGridView を継承した独自のデータグリッドビューコントロールです。
    /// </summary>
    public class DataGridViewEX : DataGridView
    {
        #region Property
        /// <summary>
        /// 行番号を表示するかどうかを取得または設定します。
        /// </summary>
        [Category("Framework")]
        [DefaultValue(GridRowNumber.None)]
        [Description("行番号を表示するかどうかを設定します。")]
        public GridRowNumber ShowRowNumber { get; set; }

        /// <summary>
        /// ユーザーは Clipboard のテキスト値をペーストできるかどうかを取得または設定します。
        /// </summary>
        [Category("Framework")]
        [DefaultValue(true)]
        [Description("ユーザーは Clipboard のテキスト値をペーストできるかどうかを取得または設定します。")]
        public bool EnableClipboardPaste { get; set; }

        /// <summary>
        /// ユーザーは Delete キーでセルの値を削除できるかどうかを取得または設定します。
        /// </summary>
        [Category("Framework")]
        [DefaultValue(true)]
        [Description("ユーザーは Delete キーでセルの値を削除できるかどうかを取得または設定します。")]
        public bool EnableCellDeleteKey { get; set; }

        /// <summary>
        /// ユーザーは Ctrl + 0 キーでセルの値を null に設定できるかどうかを取得または設定します。
        /// </summary>
        [Category("Framework")]
        [DefaultValue(false)]
        [Description("ユーザーは Ctrl + 0 キーでセルの値を null に設定できるかどうかを取得または設定します。")]
        public bool EnableCtrl0 { get; set; }

        /// <summary>
        /// System.Windows.Forms.DataGridView のセルを選択できるかどうかを示す値を取得または設定します。
        /// </summary>
        /// <remarks>
        /// 基底クラスの SelectionMode プロパティに初期値を設定しました。
        /// </remarks>
        [DefaultValue(DataGridViewSelectionMode.CellSelect)]
        public new DataGridViewSelectionMode SelectionMode
        {
            get { return base.SelectionMode; }
            set { base.SelectionMode = value; }
        }
        #endregion

        #region Constructor
        /// <summary>
        /// DataGridViewEx クラスの新しいインスタンスを初期化します。
        /// </summary>
        public DataGridViewEX()
        {
            EnableClipboardPaste = true;
            EnableCellDeleteKey = true;
            EnableCtrl0 = false;
            SelectionMode = DataGridViewSelectionMode.CellSelect;
        }
        #endregion

        #region Event
        /// <summary>
        /// セルが描画されなければならないときに発生します。
        /// </summary>
        /// <param name="e" type="DataGridViewCellPaintingEventArgs">DataGridViewCellPaintingEventArgs クラスのインスタンスを設定します。</param>
        protected override void OnCellPainting(DataGridViewCellPaintingEventArgs e)
        {
            PaintRowNumber(e);

            base.OnCellPainting(e);
        }

        /// <summary>
        /// セルの一部がクリックされたときに発生します。
        /// </summary>
        /// <param name="e">DataGridViewCellEventArgs クラスのインスタンスを設定します。</param>
        protected override void OnCellClick(DataGridViewCellEventArgs e)
        {
            base.OnCellClick(e);

            // ヘッダーは無視します。
            if (e.ColumnIndex < 0 || e.RowIndex < 0)
                return;

            // ドロップダウンがクリックされた場合はワンクリックで項目値を表示します。
            var currentCell = this[e.ColumnIndex, e.RowIndex];
            if (currentCell is DataGridViewComboBoxCell && !currentCell.ReadOnly)
            {
                BeginEdit(true);
                ((DataGridViewComboBoxEditingControl)EditingControl).DroppedDown = true;
            }
        }

        /// <summary>
        /// グリッドにフォーカスがあるときにキーが押されると発生します。
        /// </summary>
        /// <param name="e" type="KeyEventArgs">KeyEventArgs クラスのインスタンスを設定します。</param>
        protected override void OnKeyDown(KeyEventArgs e)
        {
            // ペースト機能が有効の場合は Ctrl + v キーで、選択されているセルにクリップボードの値を設定します。
            if (EnableClipboardPaste && e.Control && !e.Shift && !e.Alt && e.KeyCode == Keys.V)
            {
                // クリップボードの値を改行コードとタブで分割します。
                // splitText には下記例のような値が設定されます。
                // 例：クリップボードの値が [JPY \t 100 \t ja-jp \r\n USD \t 200 \t en-us] の場合
                // [JPY][100][ja-jp]
                // [USD][200][en-us]
                string planeText = Clipboard.GetText();

                var splitText = new List<string[]>();
                foreach (var lineText in planeText.Split(new[] { Environment.NewLine }, StringSplitOptions.None))
                    splitText.Add(lineText.Split('\t'));

                if (splitText.Count == 1 && splitText[0].Length == 1)
                {
                    // クリップボードに改行コードとタブが含まれない場合は、選択している全てのセルに同じ値を設定します。
                    foreach (DataGridViewCell cell in SelectedCells)
                    {
                        if (cell.ReadOnly || !cell.Visible)
                            continue;

                        // todo: クリップボードの値をセルに設定したあとセルを編集モードにすることによって、一連の検証イベントを発生させます。
                        // クリップボードの値をセルに設定します。
                        if (cell is DataGridViewTextBoxCell)
                        {
                            cell.Value = splitText[0][0];
                        }
                        else if (cell is DataGridViewComboBoxCell)
                        {
                            dynamic items = ((DataGridViewComboBoxCell)cell).Items;
                            foreach (var item in items)
                            {
                                if (item.Key == splitText[0][0])
                                {
                                    cell.Value = item.Value;
                                    break;
                                }
                            }
                        }
                    }
                }
                else
                {
                    // クリップボードに改行コードまたはタブが含まれる場合は、選択している最初の左上のセルから値を設定します。
                    // 選択されているセルの左上の位置を取得します。
                    int firstRowIndex = int.MaxValue;
                    int firstColumnIndex = int.MaxValue;
                    foreach (DataGridViewCell cell in SelectedCells)
                    {
                        if (cell.RowIndex < firstRowIndex)
                            firstRowIndex = cell.RowIndex;

                        if (cell.ColumnIndex < firstColumnIndex)
                            firstColumnIndex = cell.ColumnIndex;
                    }

                    int rowSkipCount = 0;
                    for (int rowIndex = 0; rowIndex - rowSkipCount < splitText.Count && rowIndex < RowCount - firstRowIndex; rowIndex++)
                    {
                        if (Rows[rowIndex + firstRowIndex].ReadOnly)
                        {
                            continue;
                        }

                        if (!Rows[rowIndex + firstRowIndex].Visible)
                        {
                            // クリップボードには非表示行がコピーされないため、カウンターを設定して次の行に移動します。
                            rowSkipCount++;
                            continue;
                        }

                        int columnSkipCount = 0;
                        for (int columnIndex = 0;
                            columnIndex - columnSkipCount < splitText[rowIndex - rowSkipCount].Length &&
                            columnIndex < Columns.Count - firstColumnIndex;
                            columnIndex++)
                        {
                            if (!Columns[columnIndex + firstColumnIndex].Visible)
                            {
                                // クリップボードには非表示列がコピーされないため、カウンターを設定して次の列に移動します。
                                columnSkipCount++;
                                continue;
                            }

                            var cell = this[columnIndex + firstColumnIndex, rowIndex + firstRowIndex];
                            if (cell.ReadOnly || !cell.Visible)
                                continue;

                            // todo: クリップボードの値をセルに設定したあとセルを編集モードにすることによって、一連の検証イベントを発生させます。
                            // クリップボードの値をセルに設定します。
                            if (cell is DataGridViewTextBoxCell)
                            {
                                cell.Value = splitText[rowIndex - rowSkipCount][columnIndex - columnSkipCount];
                            }
                            else if (cell is DataGridViewComboBoxCell)
                            {
                                dynamic items = ((DataGridViewComboBoxCell)cell).Items;
                                foreach (var item in items)
                                {
                                    if (item.Key == splitText[rowIndex - rowSkipCount][columnIndex - columnSkipCount])
                                    {
                                        cell.Value = item.Value;
                                        break;
                                    }
                                }
                            }
                        }
                    }
                }
            }

            // セル削除機能が有効の場合は Delete キーで、選択されているセルの値を削除します。
            if (EnableCellDeleteKey && e.KeyCode == Keys.Delete)
            {
                // セルの値を削除後に選択されているセルを元に戻すためにセルを保管します。
                var backupSelectedCells = new List<DataGridViewCell>();
                foreach (DataGridViewCell cell in SelectedCells)
                {
                    if (!cell.Visible)
                        continue;

                    backupSelectedCells.Add(cell);

                    if (cell.ReadOnly)
                        continue;

                    if (cell is DataGridViewTextBoxCell)
                    {
                        cell.Value = null;
                    }
                    else if (cell is DataGridViewComboBoxCell)
                    {
                        var ddlCell = (DataGridViewComboBoxCell)cell;
                        if (ddlCell.Items.Count > 0)
                            cell.Value = ((dynamic)ddlCell.Items[0]).Value;
                    }
                }

                // 選択されているセルを元に戻します。
                foreach (DataGridViewCell cell in backupSelectedCells)
                    cell.Selected = true;
            }

            // 「Ctrl + 0」キーを無効にします。
            if (!EnableCtrl0 && e.Control && (e.KeyCode == Keys.D0 || e.KeyCode == Keys.NumPad0))
                e.Handled = true;

            base.OnKeyDown(e);
        }

        /// <summary>
        /// セルを編集するためのコントロールが表示されている場合に発生します。
        /// </summary>
        /// <param name="e">DataGridViewEditingControlShowingEventArgs クラスのインスタンスを設定します。</param>
        protected override void OnEditingControlShowing(DataGridViewEditingControlShowingEventArgs e)
        {
            if (!EnableCtrl0)
            {
                e.Control.PreviewKeyDown -= IgnoreCtrl0;
                e.Control.PreviewKeyDown += IgnoreCtrl0;
            }

            base.OnEditingControlShowing(e);
        }
        #endregion

        #region Helper
        /// <summary>
        /// 行番号を描画します。
        /// </summary>
        private void PaintRowNumber(DataGridViewCellPaintingEventArgs e)
        {
            if (ShowRowNumber == GridRowNumber.None)
                return;

            // 表示しようとしているセルがヘッダーかどうかの確認と、行番号のレイアウトを設定します。
            var isHeader = (e.ColumnIndex < 0 && e.RowIndex >= 0);
            if (isHeader)
            {
                // セルを描画する
                e.Paint(e.ClipBounds, DataGridViewPaintParts.All);

                // 行番号を描画する範囲を決定する
                Rectangle indexRect = e.CellBounds;
                indexRect.Inflate(-2, -2);

                // 行番号を描画する
                var textFormatFlags = TextFormatFlags.Right | TextFormatFlags.VerticalCenter;
                var rowNumber = GetRowNumberText(e.RowIndex + 1);
                TextRenderer.DrawText(e.Graphics, rowNumber, e.CellStyle.Font, indexRect, e.CellStyle.ForeColor, textFormatFlags);

                // 描画が完了したことを知らせる
                e.Handled = true;
            }
        }

        /// <summary>
        /// 指定した行番号に表示する文字列を取得します。
        /// </summary>
        private string GetRowNumberText(int rowIndex)
        {
            if (ShowRowNumber == GridRowNumber.Numerical)
                return (rowIndex).ToString();

            if (ShowRowNumber == GridRowNumber.Alphabet)
                return Formater.ToExcelAlphabet(rowIndex);

            return "";
        }

        /// <summary>
        /// 「Ctrl + 0」キーを押下時、IsInputKey プロパティを true に設定します。
        /// これにより、KeyDown, KeyUp イベントが発生すると共に、本来の「Ctrl + 0」の機能が無効となります。
        /// </summary>
        private void IgnoreCtrl0(object sender, PreviewKeyDownEventArgs e)
        {
            if (e.Control && (e.KeyCode == Keys.D0 || e.KeyCode == Keys.NumPad0))
                e.IsInputKey = true;
        }
        #endregion
    }

    #region Enum
    /// <summary>
    /// グリッドに表示する行番号を定義する定数を指定します。
    /// </summary>
    public enum GridRowNumber
    {
        /// <summary>
        /// 行番号を表示しません。
        /// </summary>
        None = 0,

        /// <summary>
        /// 行番号を数値で表示します。
        /// </summary>
        Numerical,

        /// <summary>
        /// 行番号をアルファベットで表示します。
        /// </summary>
        Alphabet,
    }
    #endregion
}